﻿using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace opentel.hub
{
    using opentel.hub.data;
    using SIPSorcery.Media;
    using SIPSorcery.Net;
    using SIPSorcery.SIP;
    using SIPSorcery.SIP.App;
    using SIPSorceryMedia.Abstractions;
    using System.Collections.Concurrent;
    using System.Net;

    // 接线员
    internal class telephonist
    {
        internal string Caller { get; set; }
        internal string Answer { get; set; }
        internal string CallID { get; set; }
        internal SIPUserAgent ua { get; set; }
        internal SIPServerUserAgent uas { get; set; }
        internal SIPTransport transport { get; set; }
        internal OpenTelServer server { get; set; }
        internal VoIPMediaSession rtpSession { get; set; }
        internal bool Calling => !rtpSession.IsClosed || ua.IsCallActive;
        internal SIPAccountInfo from { get; set; }
        internal SIPAccountInfo to { get; set; }
        internal SDP sdp_offer { get; set; }
        internal SDP sdp_answer { get; set; }
        internal SIPRequest sipRequest { get; set; }
        internal SIPCallDescriptor callDescriptor { get; set; }
        internal List<AudioCodecsEnum> codecs { get; set; }
        internal ConcurrentBag<bool> bag_notclosed = new ConcurrentBag<bool>();

        internal bool OK = false;
        public telephonist(OpenTelServer server, SIPRequest sipRequest)
        {
            var from = server.contact.FindFrom(sipRequest);
            if (from == null) return;
            var to = server.contact.FindTo(sipRequest);
            if (to == null) return;

            this.CallID = sipRequest.Header.CallId;
            bag_notclosed.Add(true);
            if (to.status != AccountStatus.Free || from.status != AccountStatus.Free)
            {
                var _call = server.calling.FindByCallId(CallID);
                if (_call != null) return;
            }
            this.server = server;
            this.sipRequest = sipRequest;
            this.from = from;
            this.to = to;
            this.transport = server.transport;
            this.Caller = from.SIPAccount.SIPUsername;

            codecs = new List<AudioCodecsEnum> { AudioCodecsEnum.PCMU, AudioCodecsEnum.PCMA, AudioCodecsEnum.G722 };

            this.OK = true;
        }
        /// <summary>
        /// 创建agent
        /// </summary>
        internal void create_agent()
        {
            var audioSource = AudioSourcesEnum.SineWave;
            Enum.TryParse(from.RegisteredContact.User, out audioSource);

            // init 数据传输
            var audioExtrasSource = new AudioExtrasSource(new AudioEncoder(), new AudioSourceOptions { AudioSource = audioSource });
            audioExtrasSource.RestrictFormats(formats => codecs.Contains(formats.Codec));
            rtpSession = new VoIPMediaSession(new MediaEndPoints { AudioSource = audioExtrasSource });
            rtpSession.AcceptRtpFromAny = true;
            rtpSession.OnRtpClosed += RtpSession_OnRtpClosed;
            rtpSession.OnRtpPacketReceived += RtpSession_OnRtpPacketReceived;
            rtpSession.OnTimeout += RtpSession_OnTimeout;
            //rtpSession.CreateOffer(IPAddress.Any);

            // init 用户代理 呼叫者
            var uasTransaction = new UASInviteTransaction(transport, sipRequest, null, false);
            uas = new SIPServerUserAgent(transport, null, uasTransaction, from.SIPAccount);
            uas.CallCancelled += Uas_CallCancelled;

            // init 用户代理 接听者
            ua = new SIPUserAgent(transport, null);
            ua.OnCallHungup += OnHangup;
            ua.ServerCallCancelled += Ua_ServerCallCancelled;
            ua.OnDtmfTone += Ua_OnDtmfTone;
            ua.OnRtpEvent += Ua_OnRtpEvent;
            ua.OnTransactionTraceMessage += Ua_OnTransactionTraceMessage;
            ua.ServerCallRingTimeout += Ua_ServerCallRingTimeout;
            ua.ClientCallAnswered += Ua_ClientCallAnswered;
            ua.ClientCallFailed += Ua_ClientCallFailed;
            ua.ClientCallRinging += Ua_ClientCallRinging;

            ua.AcceptCall(sipRequest);
            to.status = AccountStatus.AnswerRinging;
            from.status = AccountStatus.CallerRinging;

            sdp_offer = SDP.ParseSDPDescription(sipRequest.Body);
            rtpSession.SetRemoteDescription(SdpType.answer, sdp_offer);

            sdp_answer = rtpSession.CreateAnswer(to.RemoteEndPoint.Address);
            rtpSession.SetRemoteDescription(SdpType.offer, sdp_answer);

            Console.WriteLine($"发送方: {sdp_answer.ToString()}");
            Console.WriteLine($"接受方: {sdp_offer.ToString()}");
        }

        internal async Task<bool> call(int timeout)
        {
            Console.WriteLine("wait ringing");
            sdp_offer = SDP.ParseSDPDescription(sipRequest.Body);
            callDescriptor = new SIPCallDescriptor(from.SIPAccount.SIPUsername, from.SIPAccount.SIPPassword, to.RegisteredContact.ToString(), from.RegisteredContact.ToString(), to.RegisteredContact.CanonicalAddress, null, null, null, SIPCallDirection.Out, SDP.SDP_MIME_CONTENTTYPE, sdp_offer.ToString(), null);

            await ua.Answer(uas, rtpSession);
            uas.Answer(SDP.SDP_MIME_CONTENTTYPE, sdp_answer.ToString(), null, SIPDialogueTransferModesEnum.Default);
            from.status = AccountStatus.Caller;
            to.status = AccountStatus.Answer;
            send_ringing();

            var ok = await ua.Call(callDescriptor, rtpSession, timeout);
            //if (ok) await rtpSession.Start();
            Console.WriteLine("end ringing");
            return ok;
        }
        public void Close()
        {
            bag_notclosed.TryPeek(out var can);
            if (!can) return;

            server.calling.RemoveByCallId(CallID);
            Console.WriteLine("hangup now!");
            from.status = AccountStatus.Free;
            to.status = AccountStatus.Free;
            rtpSession.Close("hangup");
        }
        internal void send_ringing()
        {
            //告知 caller振铃
            uas.Progress(SIPResponseStatusCodesEnum.Ringing, "Ringing", null, null, null);
        }
        internal void send_trying()
        {
            uas.Progress(SIPResponseStatusCodesEnum.Trying, "Trying", null, null, null);
        }
        internal void send_ok()
        {
            uas.Progress(SIPResponseStatusCodesEnum.Ok, "Ok", null, null, null);
        }
        internal void send_unavailable()
        {
            uas.Progress(SIPResponseStatusCodesEnum.TemporarilyUnavailable, "TemporarilyUnavailable", null, null, null);
        }
        private void RtpSession_OnRtpClosed(string reason)
        {
            uas?.Hangup(reason == "hangup");
            ua?.Hangup();
        }

        private void RtpSession_OnTimeout(SDPMediaTypesEnum obj)
        {
            //Close();
        }
        private void RtpSession_OnRtpPacketReceived(IPEndPoint arg1, SDPMediaTypesEnum arg2, RTPPacket arg3)
        {

        }
        private void Ua_ClientCallRinging(ISIPClientUserAgent uac, SIPResponse sipResponse)
        {
        }
        private void Ua_ClientCallFailed(ISIPClientUserAgent uac, string errorMessage, SIPResponse sipResponse)
        {
            Close();
        }
        private void Ua_ClientCallAnswered(ISIPClientUserAgent uac, SIPResponse sipResponse)
        {
        }
        private void Ua_OnTransactionTraceMessage(SIPTransaction tx, string msg)
        {
            Console.WriteLine($"uas tx {tx.TransactionId}: {msg}");
        }

        private void Ua_OnRtpEvent(RTPEvent evt, RTPHeader hdr)
        {
            Console.WriteLine($"rtp event {evt.EventID}, duration {evt.Duration}, end of event {evt.EndOfEvent}, timestamp {hdr.Timestamp}, marker {hdr.MarkerBit}.");
        }

        private void Ua_OnDtmfTone(byte arg1, int arg2)
        {
            OnDtmfTone(ua, arg1, arg2);
        }

        private void Ua_ServerCallCancelled(ISIPServerUserAgent uas, SIPRequest cancelRequest)
        {
            Console.WriteLine("Incoming call cancelled by remote party.");
        }

        private void Uas_CallCancelled(ISIPServerUserAgent uas, SIPRequest cancelRequest)
        {
            // uas cancel
            Close();
        }

        private void Ua_ServerCallRingTimeout(ISIPServerUserAgent uas)
        {
            // uas ringing timeout
            Close();
        }

        private void OnHangup(SIPDialogue dialogue)
        {
            if (dialogue != null) this.ua.Close();

        }
        private static void OnDtmfTone(SIPUserAgent ua, byte key, int duration)
        {
            string callID = ua.Dialogue.CallId;
            Console.WriteLine($"Call {callID} received DTMF tone {key}, duration {duration}ms.");
        }
    }
}
